﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using Go;
using GoRpc;

namespace FileService
{
    class Program
    {
        static void Main(string[] ps)
        {
            string ip = ps[0];
            int port = int.Parse(ps[1]);
            work_service work = new work_service();
            work_strand strand = new work_strand(work);
            shared_strand ioStrand = new shared_strand();
            generator.go(strand, async delegate ()
            {
                generator.children sessionChild = new generator.children();
reaccep:
                socket_tcp.acceptor accep = new socket_tcp.acceptor();
                if (!accep.bind(ip, port, 100))
                {
                    await generator.sleep(3000);
                    goto reaccep;
                }
                Dictionary<long, Rpc.Session> sessDict = new Dictionary<long, Rpc.Session>();
                long tokenCount = system_tick.get_tick();
                while (true)
                {
                    socket_tcp socket = new socket_tcp();
                    if (!await accep.accept(socket))
                    {
                        return;
                    }
                    Rpc.Methods methods = new Rpc.Methods();
                    Rpc.Session session = new Rpc.Session(strand, socket, methods);
                    long token = tokenCount++;
                    sessDict.Add(token, session);
                    sessionChild.free_go(async delegate ()
                    {
                        int block = 1024;
                        Console.WriteLine("new session");
                        await generator.send_task(delegate ()
                        {
                            methods.Bind("GetToken", Rpc.Methods.Cast(delegate ()
                            {
                                return new ValueTask<long>(token);
                            }));
                            methods.Bind("SetBlock", Rpc.Methods.Cast(delegate (int v)
                            {
                                Console.WriteLine($"SetBlock {v}KB");
                                block = v;
                                return new ValueTask();
                            }));
                            methods.Bind("GetDrives", Rpc.Methods.Cast(async delegate ()
                            {
                                return await generator.send_task(delegate ()
                                {
                                    return Array.ConvertAll(DriveInfo.GetDrives(), (DriveInfo drive) => drive.Name);
                                });
                            }));
                            methods.Bind("GetFiles", Rpc.Methods.Cast(async delegate (string path)
                            {
                                return await generator.send_task(delegate ()
                                {
                                    return Array.ConvertAll(Directory.GetFiles(path), (string v) => Path.GetFileName(v));
                                });
                            }));
                            methods.Bind("GetDirectories", Rpc.Methods.Cast(async delegate (string path)
                            {
                                return await generator.send_task(delegate ()
                                {
                                    return Array.ConvertAll(Directory.GetDirectories(path), (string v) => Path.GetFileName(v));
                                });
                            }));
                            methods.Bind("FileSize", Rpc.Methods.Cast(async delegate (string path)
                            {
                                return await generator.send_task(delegate ()
                                {
                                    return new FileInfo(path).Length;
                                });
                            }));
                            methods.Bind("FileInfo", Rpc.Methods.Cast(async delegate (string path)
                            {
                                return await generator.send_strand(ioStrand, delegate ()
                                {
                                    return new DateTime[]
                                    {
                                        File.GetLastWriteTime(path),
                                        File.GetCreationTime(path),
                                        File.GetLastAccessTime(path),
                                    };
                                });
                            }));
                            methods.Bind("DirectoryInfo", Rpc.Methods.Cast(async delegate (string path)
                            {
                                return await generator.send_strand(ioStrand, delegate ()
                                {
                                    return new DateTime[]
                                    {
                                        Directory.GetLastWriteTime(path),
                                        Directory.GetCreationTime(path),
                                        Directory.GetLastAccessTime(path),
                                    };
                                });
                            }));
                            methods.Bind("SetDirectoryInfo", Rpc.Methods.Cast(async delegate (string path, DateTime[] dirInfo)
                            {
                                await generator.send_strand(ioStrand, delegate ()
                                {
                                    Directory.SetLastWriteTime(path, dirInfo[0]);
                                    Directory.SetCreationTime(path, dirInfo[1]);
                                    Directory.SetLastAccessTime(path, dirInfo[2]);
                                });
                            }));
                            methods.Bind("ReadFile", Rpc.Methods.Cast(async delegate (string path, Rpc.Methods.Name writeFile)
                            {
                                generator self = generator.self;
                                Rpc.Session.CancelWait().AppendNotify(() => self.stop());
                                FileStream file = null;
                                DateTime[] info = null;
                                try
                                {
                                    generator.lock_stop();
                                    await generator.send_strand(ioStrand, delegate ()
                                    {
                                        try
                                        {
                                            file = File.OpenRead(path);
                                            info = new DateTime[]
                                            {
                                                File.GetCreationTime(path),
                                                File.GetLastWriteTime(path),
                                                File.GetLastAccessTime(path),
                                            };
                                        }
                                        catch (Exception) { }
                                    });
                                    generator.unlock_stop();
                                    if (null == file)
                                    {
                                        return false;
                                    }
                                    Console.WriteLine($"Begin Send {path}");
                                    long fileLength = file.Length;
                                    if (fileLength > 0)
                                    {
                                        Rpc.SendDoubleBuffer dbuff = new Rpc.SendDoubleBuffer(block * 1024, ioStrand);
                                        dbuff.Async(() => file.Read(dbuff.ReadBuff, 0, (int)Math.Min(dbuff.ReadBuff.Length, fileLength)));
                                        Rpc.Session.Self.OverSend(fileLength, async delegate (long offset, long count)
                                        {
                                            await dbuff.Exchange();
                                            if (dbuff.Error)
                                            {
                                                return;
                                            }
                                            var data = dbuff.SendData;
                                            if (count - data.Count > 0)
                                            {
                                                dbuff.Async(() => file.Read(dbuff.ReadBuff, 0, (int)Math.Min(dbuff.ReadBuff.Length, count - data.Count)));
                                            }
                                            await session.Send(data);
                                        });
                                    }
                                    var res = await session.Call(writeFile, info);
                                    if (!res.Success)
                                    {
                                        Console.WriteLine($"Fail Send {path}");
                                        return false;
                                    }
                                    Console.WriteLine($"End Send {path}");
                                    return true;
                                }
                                catch (generator.stop_exception)
                                {
                                    Console.WriteLine($"Cancel Send {path}");
                                    throw;
                                }
                                finally
                                {
                                    file?.Dispose();
                                }
                            }));
                            methods.Bind("ReadFileMT", Rpc.Methods.Cast(async delegate (long[] sessToken, string path, Rpc.Methods.Name writeFile)
                            {
                                generator self = generator.self;
                                Rpc.Session.CancelWait().AppendNotify(() => self.stop());
                                FileStream file = null;
                                try
                                {
                                    generator.lock_stop();
                                    await generator.send_strand(ioStrand, delegate ()
                                    {
                                        try
                                        {
                                            file = File.OpenRead(path);
                                        }
                                        catch (Exception) { }
                                    });
                                    generator.unlock_stop();
                                    if (null == file)
                                    {
                                        return false;
                                    }
                                    Console.WriteLine($"Begin Send {path}");
                                    bool completed = await TransmitFile.Send(block, writeFile, file, Array.ConvertAll(sessToken, (long v) => sessDict[v]), ioStrand, null);
                                    if (!completed)
                                    {
                                        Console.WriteLine($"Fail Send {path}");
                                        return false;
                                    }
                                    Console.WriteLine($"End Send {path}");
                                    return true;
                                }
                                catch (generator.stop_exception)
                                {
                                    Console.WriteLine($"Cancel Send {path}");
                                    throw;
                                }
                                finally
                                {
                                    file?.Dispose();
                                }
                            }));
                            methods.Bind("CreateDirectory", Rpc.Methods.Cast(async delegate (string path)
                            {
                                Console.WriteLine($"CreateDirectory {path}");
                                await generator.send_strand(ioStrand, delegate ()
                                {
                                    Directory.CreateDirectory(path);
                                });
                            }));
                            methods.Bind("CreateFile", Rpc.Methods.Cast(async delegate (string path)
                            {
                                Console.WriteLine($"CreateFile {path}");
                                await generator.send_strand(ioStrand, delegate ()
                                {
                                    File.Create(path).Dispose();
                                });
                            }));
                            methods.Bind("WriteFile", Rpc.Methods.Cast(async delegate (bool force, string dstFile, DateTime[] fileInfo)
                            {
                                if (!force && File.Exists(dstFile))
                                {
                                    Console.WriteLine($"{dstFile} 已存在");
                                    return false;
                                }
                                Rpc.OverRecv[] recvs = Rpc.Session.OverRecv();
                                FileStream file = null;
                                bool completed = false;
                                try
                                {
                                    Console.WriteLine($"Begin Recv {dstFile}");
                                    generator.lock_stop();
                                    await generator.send_strand(ioStrand, delegate ()
                                    {
                                        try
                                        {
                                            if (File.Exists(dstFile))
                                            {
                                                if (File.GetLastWriteTime(dstFile) >= fileInfo[0])
                                                {
                                                    Console.WriteLine($"{dstFile} 忽略\r\n");
                                                    return;
                                                }
                                                else
                                                {
                                                    Console.WriteLine($"{dstFile} 覆盖\r\n");
                                                }
                                            }
                                            file = File.Open(dstFile, FileMode.Create);
                                        }
                                        catch (Exception) { }
                                    });
                                    generator.unlock_stop();
                                    if (null == file)
                                    {
                                        return false;
                                    }
                                    if (null != recvs)
                                    {
                                        Rpc.RecvDoubleBuffer dbuff = new Rpc.RecvDoubleBuffer(block * 1024, ioStrand);
                                        bool first = true;
                                        long offset = 0;
                                        for (offset = 0; offset < recvs[0].Length;)
                                        {
                                            int len = await Rpc.Session.Self.Recv(recvs[0], dbuff.RecvBuff, 0, dbuff.RecvBuff.Length);
                                            if (len <= 0)
                                            {
                                                break;
                                            }
                                            await dbuff.Exchange();
                                            if (dbuff.Error)
                                            {
                                                break;
                                            }
                                            dbuff.Async(delegate ()
                                            {
                                                if (first)
                                                {
                                                    first = false;
                                                    if (0 == string.Compare(new DriveInfo(dstFile).DriveFormat, "NTFS", true))
                                                    {
                                                        file.SetLength(recvs[0].Length);
                                                    }
                                                }
                                                file.Write(dbuff.WriteData, 0, len);
                                            });
                                            offset += len;
                                        }
                                        await dbuff.Exchange();
                                        if (!dbuff.Error)
                                        {
                                            completed = offset == recvs[0].Length;
                                        }
                                        if (recvs[0].Cancel)
                                        {
                                            Console.WriteLine($"Cancel Recv {dstFile}");
                                        }
                                        else
                                        {
                                            Console.WriteLine($"End Recv {dstFile}");
                                        }
                                        return !recvs[0].Cancel;
                                    }
                                    else
                                    {
                                        completed = true;
                                        return true;
                                    }
                                }
                                finally
                                {
                                    generator.lock_stop();
                                    await generator.send_strand(ioStrand, delegate ()
                                    {
                                        if (null != file)
                                        {
                                            try
                                            {
                                                file.Dispose();
                                                if (completed)
                                                {
                                                    File.SetLastWriteTime(dstFile, fileInfo[0]);
                                                    File.SetCreationTime(dstFile, fileInfo[1]);
                                                    File.SetLastAccessTime(dstFile, fileInfo[2]);
                                                }
                                                else
                                                {
                                                    File.Delete(dstFile);
                                                }
                                            }
                                            catch (Exception) { }
                                        }
                                    });
                                    generator.unlock_stop();
                                }
                            }));
                            methods.Bind("WriteFileMT", Rpc.Methods.Cast(async delegate (long[] sessToken, bool force, string dstFile, DateTime[] fileInfo, Rpc.Methods.Name readFile)
                            {
                                if (!force && File.Exists(dstFile))
                                {
                                    Console.WriteLine($"{dstFile} 已存在\r\n");
                                    return false;
                                }
                                generator self = generator.self;
                                Rpc.Session.CancelWait().AppendNotify(() => self.stop());
                                Console.WriteLine($"Begin Recv {dstFile}");
                                bool completed = false;
                                FileStream file = null;
                                try
                                {
                                    generator.lock_stop();
                                    await generator.send_strand(ioStrand, delegate ()
                                    {
                                        try
                                        {
                                            if (File.Exists(dstFile))
                                            {
                                                if (File.GetLastWriteTime(dstFile) >= fileInfo[0])
                                                {
                                                    Console.WriteLine($"{dstFile} 忽略\r\n");
                                                    return;
                                                }
                                                else
                                                {
                                                    Console.WriteLine($"{dstFile} 覆盖\r\n");
                                                }
                                            }
                                            file = File.Open(dstFile, FileMode.Create);
                                        }
                                        catch (Exception) { }
                                    });
                                    generator.unlock_stop();
                                    if (null == file)
                                    {
                                        return false;
                                    }
                                    Rpc.Methods.Name recvName = Rpc.Methods.GenName;
                                    Rpc.Session[] sessions = Array.ConvertAll(sessToken, (long v) => sessDict[v]);
                                    wait_group recvWg = new wait_group();
                                    TransmitFile.RecvResult recvResult = new TransmitFile.RecvResult();
                                    tuple<Action, Delegate> recvHandler = TransmitFile.RecvHandler(file, recvResult, recvWg, ioStrand, null);
                                    try
                                    {
                                        for (int i = 0; i < sessions.Length; i++)
                                        {
                                            sessions[i].Methods.Bind(recvName, recvHandler.value2, generator.self_strand());
                                        }
                                        Rpc.Result<bool> result = await session.Call<bool>(readFile, recvName);
                                        if (!result.Success)
                                        {
                                            Console.WriteLine($"Fail Recv {dstFile}");
                                            return false;
                                        }
                                        Console.WriteLine($"End Recv {dstFile}");
                                        completed = recvResult.fileLength == recvResult.recvLength;
                                        return completed;
                                    }
                                    finally
                                    {
                                        generator.lock_stop();
                                        recvHandler.value1();
                                        for (int i = 0; i < sessions.Length; i++)
                                        {
                                            sessions[i].Methods.Remove(recvName);
                                        }
                                        await recvWg.wait();
                                        generator.unlock_stop();
                                    }
                                }
                                catch (generator.stop_exception)
                                {
                                    Console.WriteLine($"Cancel Recv {dstFile}");
                                    throw;
                                }
                                finally
                                {
                                    generator.lock_stop();
                                    await generator.send_strand(ioStrand, delegate ()
                                    {
                                        if (null != file)
                                        {
                                            try
                                            {
                                                file.Dispose();
                                                if (completed)
                                                {
                                                    File.SetLastWriteTime(dstFile, fileInfo[0]);
                                                    File.SetCreationTime(dstFile, fileInfo[1]);
                                                    File.SetLastAccessTime(dstFile, fileInfo[2]);
                                                }
                                                else
                                                {
                                                    File.Delete(dstFile);
                                                }
                                            }
                                            catch (Exception) { }
                                        }
                                    });
                                    generator.unlock_stop();
                                }
                            }));
                        });
                        session.Start();
                        await session.Wait();
                        sessDict.Remove(token);
                        Console.WriteLine($"free session {session.Code}");
                    });
                }
            });
            work.run();
        }
    }
}
